package com.hk.commons.util.date;

import com.hk.commons.util.ArrayUtils;
import com.hk.commons.util.ObjectUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;

import java.text.ParseException;
import java.time.*;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 日期工具类
 *
 * @author kevin
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class DateTimeUtils {

    public static final String YYYY = "YYYY";

    public static final String MM_DD = "MM-dd";

    public static final String YYYY_MM = "yyyy-MM";

    public static final String YYYY_MM_DD = "yyyy-MM-dd";

    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

    public static final String YYYYMMDD = "yyyyMMdd";

    public static final String MM_DD_HH_MM = "MM-dd HH:mm";

    public static final String MM_DD_HH_MM_SS = "MM-dd HH:mm:ss";

    public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";

    public static final String YYYY_MM_DD_HH_MM_CN = "yyyy年MM月dd日 HH:mm";

    public static final String YYYY_MM_DD_HH_MM_EN = "yyyy/MM/dd HH:mm";

    public static final String MM_DD_YY_CN = "MM/dd/yy";

    public static final String YYYY_MM_DD_HH_MM_SS_CN = "yyyy年MM月dd日 HH:mm:ss";

    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    public static final String YYYY_MM_DD_CN = "yyyy年MM月dd日";

    public static final String YYYY_MM_DD_EN = "yyyy/MM/dd";

    public static final String YYYY_MM_DD_HH_MM_SS_EN = "yyyy/MM/dd HH:mm:ss";

    public static final String MM_DD_EN = "MM/dd";

    public static final String YYYY_MM_EN = "yyyy/MM";

    public static final String MM_DD_HH_MM_EN = "MM/dd HH:mm";

    public static final String MM_DD_HH_MM_SS_EN = "MM/dd HH:mm:ss";

    public static final String MM_DD_CN = "MM月dd日";

    public static final String YYYY_MM_CN = "yyyy年MM月";

    public static final String MM_DD_HH_MM_CN = "MM月dd日 HH:mm";

    public static final String MM_DD_HH_MM_SS_CN = "MM月dd日 HH:mm:ss";

    public static final String HH_MM = "HH:mm";

    public static final String HH_MM_SS = "HH:mm:ss";

    public static final String HHMMSS = "HHmmss";

    /**
     * 月份/日期/年份 格式
     * 月份可以是1~2位数
     * 日期可以是1~2位数
     * 年份可以是2位或4位数
     * <p>
     * 如 12/18/22 ，解析为 2022年12月18日
     */
    private static final Pattern PATTERN = Pattern.compile("^\\d{1,2}/\\d{1,2}/(\\d{2}|\\d{4})$");

    /**
     * 返回 calendar所在天的开始时间
     *
     * @param calendar calendar
     * @return calendar
     */
    public static Calendar getDateWithOutTime(Calendar calendar) {
        var newDate = Calendar.getInstance();
        newDate.setTime(calendar.getTime());
        newDate.set(Calendar.HOUR_OF_DAY, 0);
        newDate.set(Calendar.MINUTE, 0);
        newDate.set(Calendar.SECOND, 0);
        newDate.set(Calendar.MILLISECOND, 0);
        return newDate;
    }

    /**
     * 返回 date 所在天的开始时间
     *
     * @param date date
     * @return calendar
     */
    public static Date getDateStartTime(Date date) {
        var newDate = Calendar.getInstance();
        newDate.setTime(date);
        newDate.set(Calendar.HOUR_OF_DAY, 0);
        newDate.set(Calendar.MINUTE, 0);
        newDate.set(Calendar.SECOND, 0);
        newDate.set(Calendar.MILLISECOND, 0);
        return newDate.getTime();
    }

    /**
     * 返回 date 所在天的结束时间
     *
     * @param date date
     * @return calendar
     */
    public static Date getDateEndTime(Date date) {
        var newDate = Calendar.getInstance();
        newDate.setTime(date);
        newDate.set(Calendar.HOUR_OF_DAY, 23);
        newDate.set(Calendar.MINUTE, 23);
        newDate.set(Calendar.SECOND, 23);
        newDate.set(Calendar.MILLISECOND, 999);
        return newDate.getTime();
    }

    /**
     * 获取指定时间这一天开始时间
     *
     * @param time time
     * @return time 这天的开始时间
     */
    public static LocalDateTime getLocalDateTimeStart(LocalDateTime time) {
        return time.toLocalDate().atTime(LocalTime.MIN);
    }

    /**
     * 获取指定时间这一天结束时间
     *
     * @param time time
     * @return time 这天的结束时间
     */
    public static LocalDateTime getLocalDateTimeEnd(LocalDateTime time) {
        return time.toLocalDate().atTime(LocalTime.MAX);
    }

    /**
     * 日期转字符串, pattern is 'yyyy-MM-dd'
     *
     * @param date date
     * @return sting
     */
    public static String dateToString(Date date) {
        return dateToString(date, DatePattern.YYYY_MM_DD);
    }

    /**
     * 日期转字符串 ,自定义pattern
     *
     * @param date    date
     * @param pattern pattern
     * @return string pattern
     */
    public static String dateToString(Date date, DatePattern pattern) {
        return DateFormatUtils.format(date, pattern.getPattern());
    }

    /**
     * 字符串转日期
     *
     * @param date date
     * @return date
     */
    public static Date stringToDate(String date) {
        return stringToDate(date, DatePattern.YYYY_MM_DD);
    }

    private static String[] datePatterns(DatePattern... patterns) {
        return Arrays.stream(patterns)
                .map(DatePattern::getPattern)
                .toArray(String[]::new);
    }

    /**
     * 字符串转日期
     *
     * @param date date
     * @return date
     */
    public static Date stringToDate(String date, DatePattern... patterns) {
        return stringToDate(date, Locale.getDefault(), patterns);
    }


    /**
     * 字符串转日期
     *
     * @param date     date
     * @param locale   local
     * @param patterns patterns
     * @return date
     */
    public static Date stringToDate(String date, Locale locale, DatePattern... patterns) {
        try {
            if (PATTERN.matcher(date).matches()) {
                return DateUtils.parseDate(date, locale, MM_DD_YY_CN);
            }
            if (ArrayUtils.isEmpty(patterns)) {
                patterns = new DatePattern[]{DatePattern.YYYY_MM_DD_HH_MM_SS, DatePattern.YYYY_MM_DD_HH_MM_SS_CN,
                        DatePattern.YYYY_MM_DD_HH_MM_SS_EN, DatePattern.YYYY_MM_DD, DatePattern.YYYY_MM_DD_HH_MM,
                        DatePattern.YYYY_MM_DD_HH_MM_CN, DatePattern.YYYY_MM_DD_HH_MM_EN};
            }
            return DateUtils.parseDate(date, locale, datePatterns(patterns));
        } catch (ParseException e) {
            throw new IllegalArgumentException(STR."[\{date} ]转换日期异常:\{e.getMessage()}");
        }
    }

    /**
     * string To LocalDateTime
     *
     * @param text     date
     * @param patterns pattern
     * @return localDateTime
     */
    public static LocalDateTime stringToLocalDateTime(String text, DatePattern... patterns) {
        if (ArrayUtils.isEmpty(patterns)) {
            return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));
        }
        var date = stringToDate(text, patterns);
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * LocalDateTime To string
     *
     * @param localDateTime localDateTime
     * @param pattern       pattern
     * @return string
     */
    public static String localDateTimeToString(LocalDateTime localDateTime, DatePattern pattern) {
        return localDateTime.format(DateTimeFormatter.ofPattern(pattern.getPattern()));
    }

    /**
     * Date To Calendar
     *
     * @param date date
     * @return Calendar
     */
    public static Calendar dateToCalendar(Date date) {
        var calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar;
    }

    /**
     * 判断两个时间是否为同一天
     *
     * @param d1 d1
     * @param d2 d2
     */
    public static boolean isSameDay(Date d1, Date d2) {
        return DateUtils.isSameDay(d1, d2);
    }

    /**
     * 判断两个时间是否为同一天
     *
     * @param d1 d1
     * @param d2 d2
     */
    public static boolean isSameDay(Date d1, LocalDateTime d2) {
        return isSameDay(d1, localDateTimeToDate(d2));
    }

    /**
     * 判断两个时间是否为同一天
     *
     * @param t1 t1
     * @param t2 t2
     */
    public static boolean isSameDay(LocalDateTime t1, LocalDateTime t2) {
        return t1.toLocalDate().isEqual(t2.toLocalDate());
    }

    /**
     * 判断两个时间是否为同一天
     *
     * @param t1 t1
     * @param t2 t2
     */
    public static boolean isSameDay(LocalDate t1, LocalDate t2) {
        return t1.isEqual(t2);
    }

    /**
     * 获取指定年第一天
     *
     * @param year year
     * @return Date
     */
    public static Date getYearMinDayToDate(int year) {
        return getYearMinDayToDate(LocalDate.ofYearDay(year, 1));
    }

    /**
     * 获取指定年第一天
     *
     * @param localDateTime localDateTime
     * @return localDateTime
     */
    public static LocalDateTime getYearMinDay(LocalDateTime localDateTime) {
        return getYearMinDay(localDateTime.toLocalDate());
    }

    /**
     * Object to String
     *
     * @param obj     obj
     * @param pattern {@link DatePattern#getPattern()}
     * @return pattern string 格式
     */
    public static String objectToString(Object obj, String pattern) {
        if (obj instanceof Date value) {
            return DateFormatUtils.format(value, pattern);
        }
        if (obj instanceof Calendar value) {
            return DateFormatUtils.format(value, pattern);
        }
        if (obj instanceof Number value) {
            return DateFormatUtils.format(value.longValue(), pattern);
        }
        if (obj instanceof YearMonth value) {
            return value.format(DateTimeFormatter.ofPattern(pattern));
        }
        if (obj instanceof ChronoLocalDateTime<?> value) {
            return value.format(DateTimeFormatter.ofPattern(pattern));
        }
        if (obj instanceof ChronoLocalDate value) {
            return value.format(DateTimeFormatter.ofPattern(pattern));
        }
        if (obj instanceof LocalTime value) {
            return value.format(DateTimeFormatter.ofPattern(pattern));
        }
        return ObjectUtils.toString(obj);
    }

    /**
     * 获取指定年第一天
     *
     * @param localDate localDate
     * @return Date
     */
    public static Date getYearMinDayToDate(LocalDate localDate) {
        return Date.from(getYearMinDay(localDate).atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 获取指定年第一天
     *
     * @param localDate localDate
     * @return LocalDateTime
     */
    public static LocalDateTime getYearMinDay(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.firstDayOfYear()).atTime(LocalTime.MIN);
    }

    /**
     * 获取指定年第一天
     *
     * @param year year
     * @return LocalDateTime
     */
    public static LocalDateTime getYearMinDay(int year) {
        return getYearMinDay(LocalDate.ofYearDay(year, 1));
    }

    /**
     * 获取指定年第一天
     *
     * @param localDateTime localDateTime
     * @return Date
     */
    public static Date getYearMinDayToDate(LocalDateTime localDateTime) {
        return getYearMinDayToDate(localDateTime.toLocalDate());
    }

    /**
     * 获取指定年最后第一天
     *
     * @param year year
     * @return Date
     */
    public static Date getYearMaxDayToDate(int year) {
        return getYearMaxDayToDate(LocalDate.ofYearDay(year, 1));
    }

    /**
     * 获取指定年最后一天
     *
     * @param localDate localDate
     * @return Date
     */
    public static Date getYearMaxDayToDate(LocalDate localDate) {
        return localDateTimeToDate(getYearMaxDay(localDate));
    }

    /**
     * 获取指定年最后一天
     *
     * @param year year
     * @return LocalDateTime
     */
    public static LocalDateTime getYearMaxDay(int year) {
        return getYearMaxDay(LocalDate.ofYearDay(year, 1));
    }

    /**
     * 获取指定年最后一天
     *
     * @param localDate localDate
     * @return LocalDateTime
     */
    public static LocalDateTime getYearMaxDay(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.lastDayOfYear()).atTime(LocalTime.MAX);
    }

    /**
     * 获取指定年最后一天
     *
     * @param localDate localDate
     * @return Date
     */
    public static Date getYearMaxDayToDate(LocalDateTime localDate) {
        return getYearMaxDayToDate(localDate.toLocalDate());
    }

    /**
     * 返回日期所在年的最后一天
     *
     * @param date date
     * @return date
     */
    public static Date getYearMaxDayToDate(Date date) {
        var dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
                .with(TemporalAdjusters.lastDayOfYear()).with(LocalTime.MAX);
        return localDateTimeToDate(dateTime);
    }

    /**
     * LocalDateTime To Date
     *
     * @param localDateTime localDateTime
     * @return Date
     */
    public static Date localDateTimeToDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * Date to LocalDateTime
     */
    public static LocalDateTime dateToLocalDateTime(Date date) {
        return Objects.isNull(date) ? null : LocalDateTime.ofInstant(date.toInstant(), ZoneOffset.systemDefault());
    }

    /**
     * Date to LocalDate
     */
    public static LocalDate dateToLocalDate(Date date) {
        return Objects.isNull(date) ? null : LocalDate.ofInstant(date.toInstant(), ZoneOffset.systemDefault());
    }

    /**
     * 返回日期所在年的第一天
     *
     * @param date date
     * @return date
     */
    public static Date getYearMinDay(Date date) {
        var dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
                .with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
        return localDateTimeToDate(dateTime);
    }

    /**
     * LocalDateTime 使用Equals 的巨坑: 会比较 getNano() 属性，一般我们在开发中，不需要严格精确到 nano
     */
    public static boolean isEqual(LocalDateTime start, LocalDateTime end) {
        if (Objects.isNull(start) || Objects.isNull(end)) {
            return false;
        }
        return isEqual(start.toLocalDate(), end.toLocalDate()) &&
                isEqual(start.toLocalTime(), end.toLocalTime());
    }

    /**
     * 只需要比较年-月-日即可
     */
    public static boolean isEqual(LocalDate start, LocalDate end) {
        if (Objects.isNull(start) || Objects.isNull(end)) {
            return false;
        }
        return start.getYear() == end.getYear() &&
                start.getMonthValue() == end.getMonthValue() &&
                start.getDayOfMonth() == end.getDayOfMonth();
    }

    /**
     * LocalTime 使用Equals 的巨坑: 会比较 getNano() 属性，一般我们在开发中，不需要严格精确到 nano
     */
    public static boolean isEqual(LocalTime start, LocalTime end) {
        if (Objects.isNull(start) || Objects.isNull(end)) {
            return false;
        }
        return start.getHour() == end.getHour() &&
                start.getMinute() == end.getMinute() &&
                start.getSecond() == end.getSecond();
    }

    /**
     * 会将 纳秒设置为 0
     *
     * @param epochMilli 如果为 null 或 <= 0 返回 null
     */
    public static LocalDateTime toLocalDateTime(Long epochMilli) {
        if (Objects.isNull(epochMilli) || epochMilli <= 0) {
            return null;
        }
        Instant instant = Instant.ofEpochMilli(epochMilli);
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
                .with(ChronoField.NANO_OF_SECOND, 0L);
    }

    /**
     * 获取两个时间的差距
     *
     * @param start 开始时间
     * @param end   结束时间
     * @return 时间差距的字符串表现形式
     */
    public static String between(Temporal start, Temporal end) {
        return durationInMillisToString(Duration.between(start, end).toMillis());
    }

    /**
     * 时间毫秒数转天-时-分-秒
     *
     * @param durationInMillis 两个时间相差的毫秒数
     * @return 时间差距的字符串表现形式
     */
    public static String durationInMillisToString(long durationInMillis) {
        long totalSeconds = durationInMillis / 1000; //秒
        if (totalSeconds < 60) {
            return STR."\{totalSeconds}秒";
        }
        if (totalSeconds == 60) {
            return "1分";
        }
        StringBuilder sb = new StringBuilder();
        int day = (int) totalSeconds / (24 * 60 * 60);
        if (day > 0) {
            sb.append(day).append("天");
            totalSeconds = totalSeconds - day * (24 * 60 * 60);
        }
        int hour = (int) totalSeconds / (60 * 60);
        if (hour > 0) {
            sb.append(hour).append("时");
            totalSeconds = totalSeconds - hour * (60 * 60);
        }

        int minutes = (int) totalSeconds / 60;
        if (minutes > 0) {
            totalSeconds = totalSeconds - minutes * 60;
        }
        if (!sb.isEmpty() || minutes > 0) {
            sb.append(minutes).append("分");
        }
        if (totalSeconds > 0) {
            sb.append(totalSeconds).append("秒");
        }
        return sb.toString();
    }

    /**
     * 返回日期所在月的最后天
     *
     * @param date date
     * @return date
     */
    public static Date getMonthMaxDate(Date date) {
        var dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
                .with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
        return localDateTimeToDate(dateTime);
    }

    /**
     * 返回日期所在月的最后天
     *
     * @param date date
     * @return date
     */
    public static LocalDateTime getMonthMaxDate(LocalDateTime date) {
        return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
    }

    /**
     * 返回日期所在月的第一天
     *
     * @param date date
     * @return date
     */
    public static LocalDateTime getMonthMinDate(LocalDateTime date) {
        return date.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
    }

    /**
     * 返回日期所在月的第一天
     *
     * @param date date
     * @return date
     */
    public static Date getMonthMinDate(Date date) {
        var dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
                .with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
        return localDateTimeToDate(dateTime);
    }

    /**
     * 返回当前日期之前或之后的几天
     *
     * @param day (day > 0) 为之后， (day < 0) 为之前， (day == 0) 为当天日期
     * @return Date
     */
    public static Date getDate(int day) {
        return DateUtils.addDays(Calendar.getInstance().getTime(), day);
    }

    /**
     * 返回当前日期之前或之后的几月
     *
     * @param month (day > 0) 为之后， (day < 0) 为之前， (day == 0) 为当天日期
     * @return Date
     */
    public static Date getMonthDate(int month) {
        return DateUtils.addMonths(Calendar.getInstance().getTime(), month);
    }

    /**
     * 返回当前日期之前或之后的几年
     *
     * @param year (day > 0) 为之后， (day < 0) 为之前， (day == 0) 为当天日期
     * @return Date
     */
    public static Date getYearDate(int year) {
        return DateUtils.addYears(Calendar.getInstance().getTime(), year);
    }
}
